﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.NetworkInformation;

namespace PurpleKnife.Library
{
    // Class to test ICMP reachability by using Echo Request.
    // Works asynchronously, sending all Echo Requests in quick succession
    
    public class CheckICMP
    {
        // property member variables, default values
        #region "Member Variables"
            // target IP address
            private IPAddress _targetAddress = null;
            // reply timeout value
            private int _timeout = 3000;                            
            // number of echo requests to send
            private int _pingCount = 3;                             
            // echo request payload
            private string _pingData = "PurpleKnife ICMP Test.";    
            // don't fragment option
            private bool _dontFragment;                             
            // TTL value
            private int _ttl = 64;                                  
            // Lock object
            private readonly object SyncRoot = new object();
            // Round trip time results list
            private List<long> RoundTripTimes = new List<long>();
            // Count of completed echo requests sequences,
            // * either successfully (reply received) or with failure (nothing or error received).
            private int completedCount = 0;        
        #endregion

        #region "Public Events"
            // CheckCompleted event
            public event EventHandler<CheckCompletedEventArgs> CheckCompleted;
        #endregion

        #region "Public Properties"

            // ICMP Echo Request Timeout in Milliseconds
            public int Timeout
            {
                get { return _timeout; }
                set
                {
                    // valid range 1 - 10000
                    if (value < 1) value = 1;
                    if (value > 10000) value = 10000;
                    _timeout = value;
                }
            }
        
            // Number of Echo Requests to send as Integer
            public int PingCount
            {
                get { return _pingCount; }
                set 
                {
                    // valid range 1 - 10000
                    if (value < 1) value = 1;
                    if (value > 10000) value = 10000;
                    _pingCount = value; 
                }
            }

            // Echo Request data to send as String
            public String PingData
            {
                get { return _pingData; }
                set { _pingData = value; }
            }

            // Echo Request Time To Live value
            public int TTL
            {
                get { return _ttl; }
                set { _ttl = value; }
            }

            // ICMP Don't Fragment
            public bool DontFragment
            {
                get { return _dontFragment; }
                set { _dontFragment = value; }
            }
        #endregion

        #region "Public Methods"
            // Start ICMP test
            public void PerformTest(IPAddress Address)
            {
                // set _Target member variable
                _targetAddress = Address;
                
                /*
                // copy internal string representation (unicode) into byte array
                // unsuitable because we want mere ASCII octets on the wire!
                //byte[] buffer = new byte[_PingData.Length * sizeof(char)];
                //System.Buffer.BlockCopy(_PingData.ToCharArray(), 0, buffer, 0, buffer.Length);
                */

                
                // clear Completed and 
                lock (SyncRoot)
                {
                    completedCount = 0;
                    RoundTripTimes.Clear();
                }
                // send _PingCount echo requests
                for (int i = 1; i <= _pingCount; i++)
                {
                    using (Ping ping = new Ping())
                    {
                        // setup complete event delegate
                        ping.PingCompleted += new PingCompletedEventHandler(PingCompleted);
                        
                        // setup ping options
                        PingOptions options = new PingOptions(_ttl, _dontFragment);
                        // create ASCII payload byte array from Unicode data string
                        byte[] buffer = System.Text.Encoding.UTF8.GetBytes(_pingData);
                        // send async ping
                        ping.SendAsync(_targetAddress, _timeout, buffer, options, null);
                    }
                }
            }
        #endregion

        #region "Member Functions"

            // Echo Reply received
            private void PingCompleted(object sender, PingCompletedEventArgs e)
            {
                // average round trip time value
                double AverageRTT = 0;
                Single PacketLoss = 0;

                // increase RX count
                lock (SyncRoot) { completedCount++; }
            
                // check ICMP status
                if (e.Reply.Status == IPStatus.Success)
                {
                    // ping succeeded, add RTT to list
                    lock (SyncRoot)
                    {
                        RoundTripTimes.Add(e.Reply.RoundtripTime);
                    }
                }

                // check for completion of all requests
                lock (SyncRoot)
                {
                    // all Pings received?
                    if (completedCount == _pingCount)
                    {
                        // yes, instanciate new CheckCompleted event args object
                        CheckCompletedEventArgs ea = new CheckCompletedEventArgs();
                    
                        // fill in event args
                        ea.Target = _targetAddress;
                        ea.PingCount = _pingCount;
                        ea.RoundTripTimes = RoundTripTimes;
                    
                        // sent at least one request and received at least one reply?
                        if (_pingCount > 0 && RoundTripTimes.Count > 0)
                        {
                            // yes. calculate average round trip time
                            for (int i = 0; i < RoundTripTimes.Count; i++)
                            {
                                AverageRTT = AverageRTT + RoundTripTimes[i];
                            }
                            AverageRTT = AverageRTT / RoundTripTimes.Count;
                            ea.AverageRTT = (int)Math.Round(AverageRTT, 0);
                        
                            // calculate packet loss
                            PacketLoss = 100 - (RoundTripTimes.Count / _pingCount) * 100;
                            ea.PacketLoss = PacketLoss;
                        }
                        else
                        {
                            // nope. no success. return zero; 100 percent packet loss
                            ea.AverageRTT = 0;
                            ea.PacketLoss = 100;
                        }

                        // raise CheckCompleted event   
                        OnCheckCompleted(ea);
                    }
                }
            }

            // RaiseEvent helper
            protected virtual void OnCheckCompleted(CheckCompletedEventArgs e)
            {
                // raise the event
                EventHandler<CheckCompletedEventArgs> handler = CheckCompleted;
                if (handler != null)
                {
                    handler(this, e);
                }
            }
        #endregion

        #region "Completed Event Args"
            // CheckCompleted event arguments class
            public class CheckCompletedEventArgs : EventArgs
            {
                public IPAddress Target { get; set; }
                public int PingCount { get; set; }
                public List<long> RoundTripTimes;
                public int AverageRTT { get; set; }
                public Single PacketLoss { get; set; }
            }
        #endregion
    
    }
}
